# --------------------------------------------------------------------------------
#  NoiseTube Web application
#  
#  Copyright (C) 2008-2010 SONY Computer Science Laboratory Paris
#  Portions contributed by Vrije Universiteit Brussel (BrusSense team), 2008-2011
# --------------------------------------------------------------------------------
#  This library is free software; you can redistribute it and/or modify it under
#  the terms of the GNU Lesser General Public License, version 2.1, as published
#  by the Free Software Foundation.
#  
#  This library is distributed in the hope that it will be useful, but WITHOUT
#  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
#  details.
#  
#  You should have received a copy of the GNU Lesser General Public License along
#  with this library; if not, write to:
#    Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor,
#    Boston, MA  02110-1301, USA.
#  
#  Full GNU LGPL v2.1 text: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
#  NoiseTube project source code repository: http://code.google.com/p/noisetube
# --------------------------------------------------------------------------------
#  More information:
#   - NoiseTube project website: http://www.noisetube.net
#   - Sony Computer Science Laboratory Paris: http://csl.sony.fr
#   - VUB BrusSense team: http://www.brussense.be
# --------------------------------------------------------------------------------
 
module GeoFeatures

  require 'net/http'
  require 'xmlsimple'
  require 'geokit'
  require 'rgeo'

  PROJECTION_SRID = 4326

  #coverage - calculates the distance of the points
  def self.coverage(geodata)
    sum=0
    last=nil
    geodata.each{ |geom|
      unless (geom.nil?)
        sum+=geom.distance(last) unless last.nil? #ellipsoidal_distance uses WGS84 ellipsoid by default
        last=geom
      end
    }
    return sum
  end
  
  def self.envelope(geodata)
    geodata.compact! #remove nil values from array
    return nil if (geodata.empty?)
    return GeometryCollection.from_geometries(geodata).bounding_box
   end


  def self.to_box(geodata,margin=0.00001)
    box=self.envelope_rgeo(geodata)
    return nil if (box.nil?)
    # 4 corners of the image
    ne = box.ne_point
    sw = box.sw_point
    nw = box.nw_point
    se = box.se_point

    # MARGIN for image if (height==0) puts "one point"
    ne2 = ne.factory.point(ne.x+margin, ne.y+margin)
    sw2 = sw.factory.point(sw.x-margin, sw.y-margin)

    return sw.factory.polygon(sw.factory.linear_ring([sw2, se, ne2, nw, sw2]))
  end


  #TODO mmstevens FIX THIS, it uses the coordinate system of Ghana(???): SRID=2136
  def self.correct_track(track)
    sql="update measures set corrected=subquery2.corrected_geom, segment_id=subquery2.seg_id, corrected_distance= ST_DISTANCE(ST_transform(measures.geom,2163), ST_transform(subquery2.corrected_geom, 2163)) from(SELECT DISTINCT ON (pt_id) pt_id, seg_id,
   ln_name,
   pt_geom,
   ST_line_interpolate_point(
       ln_geom,
       ST_line_locate_point(ln_geom, pt_geom)
     )
   as corrected_geom

 FROM
   (
SELECT
    DIstinct on (pt_id)
     m.id AS pt_id,
     /*r.id AS ln_id,*/
     s.id AS seg_id,
     s.geom AS ln_geom,
     m.geom AS pt_geom,
     r.name AS ln_name,
     ST_Distance(s.geom, m.geom)  as d
   FROM
     measures m

     inner JOIN segments s on ST_DWithin(m.geom, s.geom, 0.0002)
     LEFT JOIN roads r on s.urban_id=r.id
   WHERE
     m.track_id=#{track.id} and
     m.geom is not null  and loudness>0 order by pt_id, d
   ) As subquery
   ) As subquery2    where measures.id = subquery2.pt_id;"

    r=ActiveRecord::Base.connection().execute(sql);
    return r.cmdtuples()

  end



=begin
Same query as above but formatted more readable

UPDATE 	measures
	SET	corrected = subquery2.corrected_geom,
		segment_id = subquery2.seg_id,
		corrected_distance = ST_DISTANCE(ST_transform(measures.geom, 2163), ST_transform(subquery2.corrected_geom, 2163))
	FROM
	(	SELECT 	DISTINCT ON	(pt_id) pt_id,
					seg_id,
					ln_name,
					pt_geom,
					ST_line_interpolate_point(ln_geom, ST_line_locate_point(ln_geom, pt_geom)) AS corrected_geom

			FROM
			(	SELECT	DISTINCT ON	(pt_id)
							m.id AS pt_id,
							/*r.id AS ln_id,*/
							s.id AS seg_id,
							s.geom AS ln_geom,
							m.geom AS pt_geom,
							r.name AS ln_name,
							ST_Distance(s.geom, m.geom) AS d
					FROM
						measures m
						INNER JOIN segments s ON ST_DWithin(m.geom, s.geom, 0.0002)
						LEFT JOIN roads r on s.urban_id=r.id
					WHERE
						m.track_id=#{track.id} AND
						m.geom IS NOT NULL AND
						loudness > 0
					ORDER BY
						pt_id,
						d
			) AS subquery
	) AS subquery2
	WHERE measures.id = subquery2.pt_id;

=end


#Commented out methods:

=begin


  desc "linking geo measures to urban element (segments)"
  task(:urbanellink=>:environment) do
    time_start=Time.now
    sql="update measures set corrected=subquery2.corrected_geom, urban_id=subquery2.ln_id, corrected_distance= ST_DISTANCE(ST_transform(measures.geom,2163), ST_transform(subquery2.corrected_geom, 2163)) from(SELECT DISTINCT ON (pt_id) pt_id, ln_id,
   ln_name,
   pt_geom,
   ST_line_interpolate_point(
       ln_geom,
       ST_line_locate_point(ln_geom, pt_geom)
     )
   as corrected_geom

 FROM
   (
SELECT
    DIstinct on (pt_id)
     m.id AS pt_id,
     r.id AS ln_id,
     r.geom AS ln_geom,
     m.geom AS pt_geom,
     r.name AS ln_name,
     ST_Distance(r.geom, m.geom)  as d
   FROM
     measures m
     LEFT JOIN tracks t on m.track_id=t.id
     LEFT JOIN roads r on ST_DWithin(m.geom, r.geom, 0.0002)
   WHERE
     m.geom is not null and loudness>0 order by pt_id, d
   ) As subquery
   ) As subquery2    where measures.id = subquery2.pt_id;"


    ActiveRecord::Base.establish_connection
    ActiveRecord::Base.connection().execute(sql);
    time_end=Time.now
    puts "time elasped #{(time_end-time_start)} "

  end



  desc "linking geo measures to urban element (segments)"
  task(:urbanseglink=>:environment) do
    puts "remove previous corrected geom"
    Measure.update_all("corrected =null")
    puts "done."


    time_start=Time.now
    # #{}sql="update measures set corrected=subquery2.corrected_geom,
    # urban_id=subquery2.seg_id from(SELECT DISTINCT ON (pt_id) pt_id, seg_id,
    sql="update measures set corrected=subquery2.corrected_geom, segment_id=subquery2.seg_id, corrected_distance= ST_DISTANCE(ST_transform(measures.geom,2163), ST_transform(subquery2.corrected_geom, 2163)) from(SELECT DISTINCT ON (pt_id) pt_id, seg_id,
   ln_name,
   pt_geom,
   ST_line_interpolate_point(
       ln_geom,
       ST_line_locate_point(ln_geom, pt_geom)
     )
   as corrected_geom

 FROM
   (
SELECT
    DIstinct on (pt_id)
     m.id AS pt_id,
     /*r.id AS ln_id,*/
     s.id AS seg_id,
     s.geom AS ln_geom,
     m.geom AS pt_geom,
     r.name AS ln_name,
     ST_Distance(s.geom, m.geom)  as d
   FROM
     measures m
     LEFT JOIN tracks t on m.track_id=t.id
     inner JOIN segments s on ST_DWithin(m.geom, s.geom, 0.0002)
     LEFT JOIN roads r on s.urban_id=r.id
   WHERE
     m.geom is not null and loudness>0 order by pt_id, d
   ) As subquery
   ) As subquery2    where measures.id = subquery2.pt_id;"


    ActiveRecord::Base.establish_connection
    ActiveRecord::Base.connection().execute(sql);
    time_end=Time.now
    puts "time elasped #{(time_end-time_start)} "

  end

 def compute_urbanelement
    res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "#{self.lat},#{self.lng}"
    res.address
    self.urbanelement=Urbanelement.find_or_create_by_name
  end

=end


  def self.envelope_rgeo(geodata)
    geodata.compact! #remove nil values from array
    return nil if (geodata.empty?)

    # Temporary factory that we use just for the bounding boxes.
    # This is a projected geographic factory. Its data appears to be in
    # lat/lon on the outside, but internally it converts to a mercator
    # projection to perform computations like bounding box.

    rgeo_factory_generator = RGeo::Geos.factory_generator(:srid => 4326) #<- has no bounding_box

    bbox_factory = RGeo::Geographic.simple_mercator_factory(:srid => 4326)

    # Data for the bbox needs to be in that factory. So assuming
    # my_points is an array of points from the ActiveRecord object
    # (which you currently have set up using the spherical_factory),
    # we cast the data to the bbox_factory for use in the bbox.
    bbox_points = geodata.map{ |p|
      RGeo::Feature.cast(p, bbox_factory)
    }

    # Now you can create a bounding box for those points.
    # Again, the ProjectedWindow class uses the projection (in this
    # case, Mercator) to define what is meant by a bounding box.
    bbox = RGeo::Geographic::ProjectedWindow.bounding_points(bbox_points)

    #casted_bbox = RGeo::Feature.cast(bbox,:factory => bbox_points[0].factory )

    bbox
  end

  #get the extents for a polygon.
  def self.polygon_extents(polygon)

    # Find the bounding box for the polygon
    envelope = polygon.envelope.exterior_ring
    p1 = envelope.point_n(0)
    p2 = envelope.point_n(2)
    min_x = p1.x
    max_x = p2.x
    min_x, max_x = max_x, min_x if min_x > max_x
    min_y = p1.y
    max_y = p2.y
    min_y, max_y = max_y, min_y if min_y > max_y

    {minx: min_x, miny: min_y, maxx: max_x, maxy: max_y}
  end

end
